package AnalizadorLexico;

import java.io.Reader;
import java.io.InputStreamReader;
import java.util.Map;
import java.util.HashMap;
import java.io.IOException;



public class AnalizadorLexico {

	private int fila=0, columna=0;
	
	public static enum CategoriaLexica {CLCOMENT,CLPAB,CLPCERR,CLID,CLASIG,CLIGUAL,CLMENOR,CLMENIG,
										CLMAYOR,CLMAYIG,CLNOT,CLDIST,CLOR,CLAND,CLMAS,CLMENOS,CLMULT,
										CLDIV,CLMOD,CLPYC,CLLITENTPOS,CLLITREAL,CLINT,CLREAL,CLIN,CLOUT,
										CLEOF,CLPVAR,CLBAB,CLBCERR,CLTBASE,CLACCCAMP,CLIF,CLELSE,CLFI,
										CLELSIF,CLTHEN,CLWHILE,CLDO,CLENDWHILE,CLTIPO,CLREC,CLENDREC,
										CLPOINTER,CLNULL,CLALLOC,CLFREE,CLFUN,CLEND,CLRETURN,CLRETURNS,CLCOMA}
	
	
	
	private static enum Estado {STINICIO,STRECCOMENT,STRECPAB,STRECPCERR,STRECID,STRECASIG,STRECIGUAL,
								STRECMENOR,STRECMENIG,STRECMAYOR,STRECMAYIG,STRECNOT,STRECDIST,STRECOR1,
								STRECOR2,STRECPVAR,STRECAND2,STRECMAS,STRECMENOS,STRECMULT,STRECDIV,
								STRECMOD,STRECPYC,STRECCERO,STRECENTERO,STRECDEC1,STRECDEC2,STRECDEC3,
								STRECEXP1,STRECEXP2,STRECEXP3,STRECEXP4,STRECEOF,STRECBAB,STRECBCERR,
								STRECTBASE,STRECACCCAMP,STRECCOMA};

	public static class Token {
	    public CategoriaLexica cat;
		public String lex;
		
		public int fila, columna;
		
	    public Token(CategoriaLexica cat) {
		    this(cat,null);
		} 	 
	    
	    public Token(CategoriaLexica cat, String lex) {
		    this.cat = cat;
			this.lex = lex;
		}
	    
	    public Token(CategoriaLexica cat, String lex, int fila, int columna) {
	    	this.cat = cat;
			this.lex = lex;
			this.fila = fila;
			this.columna = columna;
		}
	    
	    public Token(Token t) {
	    	this.cat = t.cat;
			this.lex = t.lex;
			this.fila = t.fila;
			this.columna = t.columna;
		}
	    
	    public String toString() {
		   return "<cat: "+cat+", lex: "+lex+", fila: "+fila+", columna: "+columna+">";
		} 	 
	  } 
   
   
   //ATRIBUTOS PRIVADOS
   private Estado estado;
   private Map<String,Token> tablaReservadas;
   private StringBuffer lex;
   private int sigCar;
   private Reader input;
   
   //CONSTRUCTORA
   public AnalizadorLexico(Reader input) throws IOException {
	      this.input = input;
		  sigCar = input.read();
		  lex = new StringBuffer();
		  construyeReservadas();
	   }
  
   private void construyeReservadas() {
	     tablaReservadas = new HashMap<String,Token>();
		 tablaReservadas.put("int",new Token(CategoriaLexica.CLINT));
		 tablaReservadas.put("real",new Token(CategoriaLexica.CLREAL)); 
		 tablaReservadas.put("in",new Token(CategoriaLexica.CLIN));
		 tablaReservadas.put("out",new Token(CategoriaLexica.CLOUT));
		 tablaReservadas.put("if",new Token(CategoriaLexica.CLIF));
		 tablaReservadas.put("else",new Token(CategoriaLexica.CLELSE));
		 tablaReservadas.put("endif",new Token(CategoriaLexica.CLFI));
		 tablaReservadas.put("elsif",new Token(CategoriaLexica.CLELSIF));
		 tablaReservadas.put("then",new Token(CategoriaLexica.CLTHEN));
		 tablaReservadas.put("while",new Token(CategoriaLexica.CLWHILE));
		 tablaReservadas.put("do",new Token(CategoriaLexica.CLDO));
		 tablaReservadas.put("endwhile",new Token(CategoriaLexica.CLENDWHILE));
		 tablaReservadas.put("tipo",new Token(CategoriaLexica.CLTIPO));
		 tablaReservadas.put("rec",new Token(CategoriaLexica.CLREC));
		 tablaReservadas.put("endrec",new Token(CategoriaLexica.CLENDREC));
		 tablaReservadas.put("pointer",new Token(CategoriaLexica.CLPOINTER));
		 tablaReservadas.put("null",new Token(CategoriaLexica.CLNULL));
		 tablaReservadas.put("alloc",new Token(CategoriaLexica.CLALLOC));
		 tablaReservadas.put("free",new Token(CategoriaLexica.CLFREE));
		 tablaReservadas.put("fun",new Token(CategoriaLexica.CLFUN));
		 tablaReservadas.put("end",new Token(CategoriaLexica.CLEND));
		 tablaReservadas.put("return",new Token(CategoriaLexica.CLRETURN));
		 tablaReservadas.put("returns",new Token(CategoriaLexica.CLRETURNS));
	   }
   
   public Token sigToken() throws IOException {
       estado = Estado.STINICIO;
	   iniciaLex();
	   while (true) {
	      switch(estado) {
	      	case STINICIO:	
	      		if(sigCar == '@') transita(Estado.STRECCOMENT);
	      		else if(sigCar == '(') transita(Estado.STRECPAB);
	      		else if(sigCar == ')') transita(Estado.STRECPCERR);
	      		else if(hayLetra() || sigCar == '_') transita(Estado.STRECID);
	      		else if(sigCar == '=') transita(Estado.STRECASIG);
	      		else if(sigCar == '<') transita(Estado.STRECMENOR);
	      		else if(sigCar == '>') transita(Estado.STRECMAYOR);
	      		else if(sigCar == '!') transita(Estado.STRECNOT);
	      		else if(sigCar == '|') transita(Estado.STRECOR1);
	      		else if(sigCar == '&') transita(Estado.STRECPVAR);
	      		else if(sigCar == '+') transita(Estado.STRECMAS);
	      		else if(sigCar == '-') transita(Estado.STRECMENOS);
	      		else if(sigCar == '*') transita(Estado.STRECMULT);
	      		else if(sigCar == '/') transita(Estado.STRECDIV);
	      		else if(sigCar == '%') transita(Estado.STRECMOD);
	      		else if(sigCar == ';') transita(Estado.STRECPYC);
	      		else if(sigCar == '0') transita(Estado.STRECCERO);
	      		else if(sigCar == '[') transita(Estado.STRECBAB);
	      		else if(sigCar == ']') transita(Estado.STRECBCERR);
	      		else if(sigCar == '^') transita(Estado.STRECTBASE);
	      		else if(sigCar == '.') transita(Estado.STRECACCCAMP);
	      		else if(sigCar == ',') transita(Estado.STRECCOMA);
	      		else if(hayDigitoPositivo()) transita(Estado.STRECENTERO);
	      		else if (hayIgnorable()) transitaIgnorando(Estado.STINICIO);
	            else if (hayEof()) transita(Estado.STRECEOF);
	            else error(); 			 
	            break;
	      	case STRECCOMENT:
	      		if (sigCar == '\n' || hayEof()){
	      			fila++;
	      			transita(Estado.STINICIO);
	      			columna = 0;
	      			iniciaLex();
	      		}
	      		else
	      			transita(Estado.STRECCOMENT);
	      		break;
	      	case STRECPAB:
	      		return new Token(CategoriaLexica.CLPAB, lex(), fila(), columna());
	      	case STRECPCERR:
	      		return new Token(CategoriaLexica.CLPCERR, lex(), fila(), columna());
	      	case STRECID:
	            if (hayLetra() || hayDigito() || sigCar == '_') transita(Estado.STRECID);
	            else return tokenId();
	            break;
	      	case STRECASIG:
	      		if(sigCar == '=') transita(Estado.STRECIGUAL);
	      		else
	      			return new Token(CategoriaLexica.CLASIG, lex(), fila(), columna());
	      		break;
	      	case STRECIGUAL:
	      		return new Token(CategoriaLexica.CLIGUAL, lex(), fila(), columna());
	      	case STRECMENOR:
	      		if(sigCar == '=') transita(Estado.STRECMENIG);
	      		else
	      			return new Token(CategoriaLexica.CLMENOR, lex(), fila(), columna());
	      		break;
	      	case STRECMENIG:
	      		return new Token(CategoriaLexica.CLMENIG, lex(), fila(), columna());
	      	case STRECMAYOR:
	      		if(sigCar == '=') transita(Estado.STRECMAYIG);
	      		else
	      			return new Token(CategoriaLexica.CLMAYOR, lex(), fila(), columna());
	      		break;
	      	case STRECMAYIG:
	      		return new Token(CategoriaLexica.CLMAYIG, lex(), fila(), columna());
	      	case STRECNOT:
	      		if(sigCar == '=') transita(Estado.STRECDIST);
	      		else
	      			return new Token(CategoriaLexica.CLNOT, lex(), fila(), columna());
	      		break;
	      	case STRECDIST:
	      		return new Token(CategoriaLexica.CLDIST, lex(), fila(), columna());
	      	case STRECOR1:
	      		if(sigCar == '|') transita(Estado.STRECOR2);
	      		else error();
	      		break;
	      	case STRECOR2:
	      		return new Token(CategoriaLexica.CLOR, lex(), fila(), columna());
	      	case STRECPVAR:
	      		if(sigCar == '&') transita(Estado.STRECAND2);
	      		else error();
	      		break;
	      	case STRECAND2:
	      		return new Token(CategoriaLexica.CLAND, lex(), fila(), columna());
	      	case STRECMAS:
	      		return new Token(CategoriaLexica.CLMAS, lex(), fila(), columna());
	      	case STRECMENOS:
	      		return new Token(CategoriaLexica.CLMENOS, lex(), fila(), columna());
	      	case STRECMULT:
	      		return new Token(CategoriaLexica.CLMULT, lex(), fila(), columna());
	      	case STRECDIV:
	      		return new Token(CategoriaLexica.CLDIV, lex(), fila(), columna());
	      	case STRECMOD:
	      		return new Token(CategoriaLexica.CLMOD, lex(), fila(), columna());
	      	case STRECPYC:
	      		return new Token(CategoriaLexica.CLPYC, lex(), fila(), columna());
	      	case STRECCOMA:
	      		return new Token(CategoriaLexica.CLCOMA, lex(), fila(), columna());
	      	case STRECCERO:
	      		if(hayDigito()) error();
	      		else return new Token(CategoriaLexica.CLLITENTPOS, lex(), fila(), columna());
	      		break;
	      	case STRECBAB:
	      		return new Token(CategoriaLexica.CLBAB, lex(), fila(), columna());
	      	case STRECBCERR:
	      		return new Token(CategoriaLexica.CLBCERR, lex(), fila(), columna());
	      	case STRECTBASE:
	      		return new Token(CategoriaLexica.CLTBASE, lex(), fila(), columna());
	      	case STRECACCCAMP:
	      		return new Token(CategoriaLexica.CLACCCAMP, lex(), fila(), columna());
	      	case STRECENTERO:
	      		if(hayDigito()) transita(Estado.STRECENTERO);
	      		else if(sigCar=='.') transita(Estado.STRECDEC1);
	      		else if(sigCar=='e'||sigCar=='E') transita(Estado.STRECEXP1);
	      		else return new Token(CategoriaLexica.CLLITENTPOS, lex(), fila(), columna());
	      		break;
	      	case STRECDEC1:
	      		if(sigCar == '0') transita(Estado.STRECDEC2);
	      		else if(hayDigitoPositivo()) transita(Estado.STRECDEC3);
	      		else error();
	      		break;
	      	case STRECDEC2:
	      		if(sigCar == '0') transita(Estado.STRECDEC2);
	      		else if(hayDigitoPositivo()) transita(Estado.STRECDEC3);
	      		else error();
	      		break;
	      	case STRECDEC3:
	      		if(sigCar == '0') transita(Estado.STRECDEC2);
	      		else if(hayDigitoPositivo()) transita(Estado.STRECDEC3);
	      		else if(sigCar=='e'||sigCar=='E') transita(Estado.STRECEXP1);
	      		else return new Token(CategoriaLexica.CLLITREAL, lex(), fila(), columna());
	      		break;
	      	case STRECEXP1:
	      		if(sigCar == '-') transita(Estado.STRECEXP2);
	      		else if(hayDigitoPositivo()) transita(Estado.STRECEXP3);
	      		else if(sigCar == '0') transita(Estado.STRECEXP4);
	      		else error();
	      		break;
	      	case STRECEXP2:
	      		if(hayDigitoPositivo()) transita(Estado.STRECEXP3);
	      		else if(sigCar == '0') transita(Estado.STRECEXP4);
	      		else error();
	      		break;
	      	case STRECEXP3:
	      		if(hayDigito()) transita(Estado.STRECEXP3);
	      		else return new Token(CategoriaLexica.CLLITREAL, lex(), fila(), columna());
	      		break;
	      	case STRECEXP4:
	      		if(hayDigito()) error();
	      		else return new Token(CategoriaLexica.CLLITENTPOS, lex(), fila(), columna());
	      		break;
	      	case STRECEOF:  
				return tokenEof();
	      		
		  }
	   }
	}   
 
 
   private Token tokenId() {
      Token t = obtenReservada();
	  if (t == null) return new Token(CategoriaLexica.CLID,lex().toLowerCase(),fila(),columna());
      else return new Token(t.cat, null, fila(), columna());	  
   }
   private Token tokenEof() {
      return new Token(CategoriaLexica.CLEOF);
   }
   private Token obtenReservada() {
     return tablaReservadas.get(lex().toLowerCase()); // paso el lexema a minusculas
   } 
   private String lex() {
     return lex.toString();
   }
   
   private void transita(Estado siguienteEstado) throws IOException {
      estado = siguienteEstado;
	  lex.append((char)sigCar);
	  // Esto es para que pare si encuentra el EOF, y no siga leyendo...
	  if(siguienteEstado != Estado.STRECEOF){ 
		  sigCar = input.read();
		  columna++;
	  }
   }
   private void transitaIgnorando(Estado siguienteEstado) throws IOException {
      estado = siguienteEstado;
      columna++;
      if (sigCar == '\n'){
    	  fila++;
    	  columna=0;
      }
	  sigCar = input.read(); 
	  
   }
   private void iniciaLex() {
      lex.delete(0,lex.length());
   }
   private boolean hayLetra() {
     return sigCar >= 'a' && sigCar <= 'z' ||
	        sigCar >= 'A' && sigCar <= 'Z';			
   }
   private boolean hayDigito() {
     return sigCar >= '0' && sigCar <= '9';			
   }
   private boolean hayDigitoPositivo() {
	     return sigCar >= '1' && sigCar <= '9';			
	   }
   
   private boolean hayEof() {
     return sigCar == -1; 		
   }
   private boolean hayIgnorable() {
     return sigCar == ' ' || sigCar == '\t' || sigCar == '\n' || sigCar == '\b' ||
	        sigCar == '\r'; 		
   }
   
   private int fila(){
	   return fila;
   }
   
   private int columna(){
	   return (columna  - lex.length());
   }
   
   
   private void error() {
     System.err.println("ERROR: Caracter no reconocido: "+sigCar);
	 System.exit(1);
   }
  
   public static void main(String[] args) throws IOException {
      AnalizadorLexico al = new AnalizadorLexico(new InputStreamReader(System.in));
	  Token t = al.sigToken();
	  while (t.cat != CategoriaLexica.CLEOF) {
	     System.out.println(t);
		 t = al.sigToken();
	  }
	  System.out.println(t);
   }
   
   
}